Completed
Push — master ( 3f33f1...179d8a )
by Ruben de
01:12
created

APIClient.promisedDecrypt   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
c 0
b 0
f 0
nc 3
dl 0
loc 20
rs 9.2
nop 2

2 Functions

Rating   Name   Duplication   Size   Complexity  
A 0 3 1
A 0 3 1
1
/* globals onLoadWorkerLoadAsmCrypto */
2
3
var _ = require('lodash'),
4
    q = require('q'),
5
    bitcoin = require('bitcoinjs-lib'),
6
    bitcoinMessage = require('bitcoinjs-message'),
7
8
    bip39 = require("bip39"),
9
    Wallet = require('./wallet'),
10
    BtccomConverter = require('./btccom.convert'),
11
    BlocktrailConverter = require('./blocktrail.convert'),
12
    RestClient = require('./rest_client'),
13
    Encryption = require('./encryption'),
14
    KeyDerivation = require('./keyderivation'),
15
    EncryptionMnemonic = require('./encryption_mnemonic'),
16
    blocktrail = require('./blocktrail'),
17
    randomBytes = require('randombytes'),
18
    CryptoJS = require('crypto-js'),
19
    webworkifier = require('./webworkifier');
20
21
/**
22
 *
23
 * @param opt
24
 * @returns {*}
25
 */
26
function networkFromOptions(opt) {
27
    if (opt.bitcoinCash) {
28
        if (opt.regtest) {
29
            return bitcoin.networks.bitcoincashregtest;
30
        } else if (opt.testnet) {
31
            return bitcoin.networks.bitcoincashtestnet;
32
        } else {
33
            return bitcoin.networks.bitcoincash;
34
        }
35
    } else {
36
        if (opt.regtest) {
37
            return bitcoin.networks.regtest;
38
        } else if (opt.testnet) {
39
            return bitcoin.networks.testnet;
40
        } else {
41
            return bitcoin.networks.bitcoin;
42
        }
43
    }
44
}
45
46
var useWebWorker = require('./use-webworker')();
47
48
49
/**
50
 * helper to wrap a promise so that the callback get's called when it succeeds or fails
51
 *
52
 * @param promise   {q.Promise}
53
 * @param cb        function
54
 * @return q.Promise
55
 */
56
function callbackify(promise, cb) {
57
    // add a .then to trigger the cb for people using callbacks
58
    if (cb) {
59
        promise
60
            .then(function(res) {
61
                // use q.nextTick for asyncness
62
                q.nextTick(function() {
63
                    cb(null, res);
64
                });
65
            }, function(err) {
66
                // use q.nextTick for asyncness
67
                q.nextTick(function() {
68
                    cb(err, null);
69
                });
70
            });
71
    }
72
73
    // return the promise for people using promises
74
    return promise;
75
}
76
77
/**
78
 * Bindings to consume the BlockTrail API
79
 *
80
 * @param options       object{
81
 *                          apiKey: 'API_KEY',
82
 *                          apiSecret: 'API_SECRET',
83
 *                          host: 'defaults to api.blocktrail.com',
84
 *                          network: 'BTC|LTC',
85
 *                          testnet: true|false
86
 *                      }
87
 * @constructor
88
 */
89
var APIClient = function(options) {
90
    var self = this;
91
92
    // handle constructor call without 'new'
93
    if (!(this instanceof APIClient)) {
94
        return new APIClient(options);
95
    }
96
97
    var normalizedNetwork = APIClient.normalizeNetworkFromOptions(options);
98
    options.network = normalizedNetwork[0];
99
    options.testnet = normalizedNetwork[1];
100
    options.regtest = normalizedNetwork[2];
101
    // apiNetwork we allow to be customized for debugging purposes
102
    options.apiNetwork = options.apiNetwork || normalizedNetwork[3];
103
104
    self.bitcoinCash = options.network === "BCC";
105
    self.regtest = options.regtest;
106
    self.testnet = options.testnet;
107
    self.network = networkFromOptions(self);
108
    self.feeSanityCheck = typeof options.feeSanityCheck !== "undefined" ? options.feeSanityCheck : true;
109
    self.feeSanityCheckBaseFeeMultiplier = options.feeSanityCheckBaseFeeMultiplier || 200;
110
111
    options.apiNetwork = options.apiNetwork || ((self.testnet ? "t" : "") + (options.network || 'BTC').toUpperCase());
112
113
    if (typeof options.btccom === "undefined") {
114
        options.btccom = true;
115
    }
116
117
    /**
118
     * @type RestClient
119
     */
120
    self.dataClient = APIClient.initRestClient(_.merge({}, options));
121
    /**
122
     * @type RestClient
123
     */
124
    self.blocktrailClient = APIClient.initRestClient(_.merge({}, options, {btccom: false}));
125
126
    if (options.btccom) {
127
        self.converter = new BtccomConverter(self.network, true);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
128
    } else {
129
        self.converter = new BlocktrailConverter();
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
130
    }
131
132
};
133
134
APIClient.normalizeNetworkFromOptions = function(options) {
135
    /* jshint -W071, -W074 */
136
    var network = 'BTC';
137
    var testnet = false;
138
    var regtest = false;
139
    var apiNetwork = "BTC";
0 ignored issues
show
Unused Code introduced by
The assignment to variable apiNetwork seems to be never used. Consider removing it.
Loading history...
140
141
    var prefix;
142
    var done = false;
143
144
    if (options.network) {
145
        var lower = options.network.toLowerCase();
146
147
        var m = lower.match(/^([rt])?(btc|bch|bcc)$/);
148
        if (!m) {
149
            throw new Error("Invalid network [" + options.network + "]");
150
        }
151
152
        if (m[2] === 'btc') {
153
            network = "BTC";
154
        } else {
155
            network = "BCC";
156
        }
157
158
        prefix = m[1];
159
        if (prefix) {
160
            // if there's a prefix then we're "done", won't apply options.regtest and options.testnet after
161
            done = true;
162
            if (prefix === 'r') {
163
                testnet = true;
164
                regtest = true;
165
            } else if (prefix === 't') {
166
                testnet = true;
167
            }
168
        }
169
    }
170
171
    // if we're not already done then apply options.regtest and options.testnet
172
    if (!done) {
173
        if (options.regtest) {
174
            testnet = true;
175
            regtest = true;
176
            prefix = "r";
177
        } else if (options.testnet) {
178
            testnet = true;
179
            prefix = "t";
180
        }
181
    }
182
183
    apiNetwork = (prefix || "") + network;
184
185
    return [network, testnet, regtest, apiNetwork];
186
};
187
188
APIClient.updateHostOptions = function(options) {
189
    /* jshint -W071, -W074 */
190
    // BLOCKTRAIL_SDK_API_ENDPOINT overwrite for development
191
    if (!options.btccom && process.env.BLOCKTRAIL_SDK_API_ENDPOINT) {
192
        options.host = process.env.BLOCKTRAIL_SDK_API_ENDPOINT;
193
    }
194
    if (options.btccom && process.env.BLOCKTRAIL_SDK_BTCCOM_API_ENDPOINT) {
195
        options.host = process.env.BLOCKTRAIL_SDK_BTCCOM_API_ENDPOINT;
196
    }
197
198
    if (options.btccom && process.env.BLOCKTRAIL_SDK_THROTTLE_BTCCOM) {
199
        options.throttleRequestsTimeout = process.env.BLOCKTRAIL_SDK_THROTTLE_BTCCOM;
200
    }
201
202
    // trim off leading https?://
203
    if (options.host && options.host.indexOf("https://") === 0) {
204
        options.https = true;
205
        options.host = options.host.substr(8);
206
    } else if (options.host && options.host.indexOf("http://") === 0) {
207
        options.https = false;
208
        options.host = options.host.substr(7);
209
    }
210
211
    if (typeof options.https === "undefined") {
212
        options.https = true;
213
    }
214
215
    if (!options.port) {
216
        options.port = options.https ? 443 : 80;
217
    }
218
219
    if (options.btccom) {
220
        if (!options.host) {
221
            options.host = 'chain.api.btc.com';
222
        }
223
224
        if (options.testnet && !options.host.match(/tchain/)) {
225
            options.host = options.host.replace(/chain/, 'tchain');
226
        }
227
228
        if (!options.endpoint) {
229
            options.endpoint = "/" + (options.apiVersion || "v3");
230
        }
231
    } else {
232
        if (!options.host) {
233
            options.host = 'api.blocktrail.com';
234
        }
235
236
        if (!options.endpoint) {
237
            options.endpoint = "/" + (options.apiVersion || "v1") + (options.apiNetwork ? ("/" + options.apiNetwork) : "");
238
        }
239
    }
240
241
    return options;
242
};
243
244
APIClient.initRestClient = function(options) {
245
    options = APIClient.updateHostOptions(options);
246
    return new RestClient(options);
247
};
248
249
var determineDataStorageV2_3 = function(options) {
250
    return q.when(options)
251
        .then(function(options) {
252
            // legacy
253
            if (options.storePrimaryMnemonic) {
254
                options.storeDataOnServer = options.storePrimaryMnemonic;
255
            }
256
257
            // storeDataOnServer=false when primarySeed is provided
258
            if (typeof options.storeDataOnServer === "undefined") {
259
                options.storeDataOnServer = !options.primarySeed;
260
            }
261
262
            return options;
263
        });
264
};
265
266
var produceEncryptedDataV2 = function(options, notify) {
267
    return q.when(options)
268
        .then(function(options) {
269
            if (options.storeDataOnServer) {
270
                if (!options.secret) {
271
                    if (!options.passphrase) {
272
                        throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase");
273
                    }
274
275
                    notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET);
276
277
                    options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase
278
                    options.encryptedSecret = CryptoJS.AES.encrypt(options.secret, options.passphrase).toString(CryptoJS.format.OpenSSL); // 'base64' string
279
                }
280
281
                notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY);
282
283
                options.encryptedPrimarySeed = CryptoJS.AES.encrypt(options.primarySeed.toString('base64'), options.secret)
284
                    .toString(CryptoJS.format.OpenSSL); // 'base64' string
285
                options.recoverySecret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase
286
287
                notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY);
288
289
                options.recoveryEncryptedSecret = CryptoJS.AES.encrypt(options.secret, options.recoverySecret)
290
                                                              .toString(CryptoJS.format.OpenSSL); // 'base64' string
291
            }
292
293
            return options;
294
        });
295
};
296
297
APIClient.prototype.promisedEncrypt = function(pt, pw, iter) {
298
    if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") {
299
        // generate randomness outside of webworker because many browsers don't have crypto.getRandomValues inside webworkers
300
        var saltBuf = Encryption.generateSalt();
301
        var iv = Encryption.generateIV();
302
303
        return webworkifier.workify(APIClient.prototype.promisedEncrypt, function factory() {
304
            return require('./webworker');
305
        }, onLoadWorkerLoadAsmCrypto, {
306
            method: 'Encryption.encryptWithSaltAndIV',
307
            pt: pt,
308
            pw: pw,
309
            saltBuf: saltBuf,
310
            iv: iv,
311
            iterations: iter
312
        })
313
            .then(function(data) {
314
                return Buffer.from(data.cipherText.buffer);
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
315
            });
316
    } else {
317
        try {
318
            return q.when(Encryption.encrypt(pt, pw, iter));
319
        } catch (e) {
320
            return q.reject(e);
321
        }
322
    }
323
};
324
325
APIClient.prototype.promisedDecrypt = function(ct, pw) {
326
    if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") {
327
        return webworkifier.workify(APIClient.prototype.promisedDecrypt, function() {
328
            return require('./webworker');
329
        }, onLoadWorkerLoadAsmCrypto, {
330
            method: 'Encryption.decrypt',
331
            ct: ct,
332
            pw: pw
333
        })
334
            .then(function(data) {
335
                return Buffer.from(data.plainText.buffer);
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
336
            });
337
    } else {
338
        try {
339
            return q.when(Encryption.decrypt(ct, pw));
340
        } catch (e) {
341
            return q.reject(e);
342
        }
343
    }
344
};
345
346
APIClient.prototype.produceEncryptedDataV3 = function(options, notify) {
347
    var self = this;
348
349
    return q.when(options)
350
        .then(function(options) {
351
            if (options.storeDataOnServer) {
352
                return q.when()
353
                    .then(function() {
354
                        if (!options.secret) {
355
                            if (!options.passphrase) {
356
                                throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase");
357
                            }
358
359
                            notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET);
360
361
                            // -> now a buffer
362
                            options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
363
364
                            // -> now a buffer
365
                            return self.promisedEncrypt(options.secret, new Buffer(options.passphrase), KeyDerivation.defaultIterations)
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
366
                                .then(function(encryptedSecret) {
367
                                    options.encryptedSecret = encryptedSecret;
368
                                });
369
                        } else {
370
                            if (!(options.secret instanceof Buffer)) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if !(options.secret instanceof Buffer) is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
371
                                throw new Error('Secret must be a buffer');
372
                            }
373
                        }
374
                    })
375
                    .then(function() {
376
                        notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY);
377
378
                        return self.promisedEncrypt(options.primarySeed, options.secret, KeyDerivation.subkeyIterations)
379
                            .then(function(encryptedPrimarySeed) {
380
                                options.encryptedPrimarySeed = encryptedPrimarySeed;
381
                            });
382
                    })
383
                    .then(function() {
384
                        // skip generating recovery secret when explicitly set to false
385
                        if (options.recoverySecret === false) {
386
                            return;
387
                        }
388
389
                        notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY);
390
                        if (!options.recoverySecret) {
391
                            options.recoverySecret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
392
                        }
393
394
                        return self.promisedEncrypt(options.secret, options.recoverySecret, KeyDerivation.defaultIterations)
395
                            .then(function(recoveryEncryptedSecret) {
396
                                options.recoveryEncryptedSecret = recoveryEncryptedSecret;
397
                            });
398
                    })
399
                    .then(function() {
400
                        return options;
401
                    });
402
            } else {
403
                return options;
404
            }
405
        });
406
};
407
408
var doRemainingWalletDataV2_3 = function(options, network, notify) {
409
    return q.when(options)
410
        .then(function(options) {
411
            if (!options.backupPublicKey) {
412
                options.backupSeed = options.backupSeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
413
            }
414
415
            notify(APIClient.CREATE_WALLET_PROGRESS_PRIMARY);
416
417
            options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.primarySeed, network);
418
419
            notify(APIClient.CREATE_WALLET_PROGRESS_BACKUP);
420
421
            if (!options.backupPublicKey) {
422
                options.backupPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.backupSeed, network);
423
                options.backupPublicKey = options.backupPrivateKey.neutered();
424
            }
425
426
            options.primaryPublicKey = options.primaryPrivateKey.deriveHardened(options.keyIndex).neutered();
427
428
            notify(APIClient.CREATE_WALLET_PROGRESS_SUBMIT);
429
430
            return options;
431
        });
432
};
433
434
APIClient.prototype.mnemonicToPrivateKey = function(mnemonic, passphrase, cb) {
435
    var self = this;
436
437
    var deferred = q.defer();
438
    deferred.promise.spreadNodeify(cb);
439
440
    deferred.resolve(q.fcall(function() {
441
        return self.mnemonicToSeedHex(mnemonic, passphrase).then(function(seedHex) {
442
            return bitcoin.HDNode.fromSeedHex(seedHex, self.network);
443
        });
444
    }));
445
446
    return deferred.promise;
447
};
448
449
APIClient.prototype.mnemonicToSeedHex = function(mnemonic, passphrase) {
450
    var self = this;
451
452
    if (useWebWorker) {
453
        return webworkifier.workify(self.mnemonicToSeedHex, function() {
454
            return require('./webworker');
455
        }, {method: 'mnemonicToSeedHex', mnemonic: mnemonic, passphrase: passphrase})
456
            .then(function(data) {
457
                return data.seed;
458
            });
459
    } else {
460
        try {
461
            return q.when(bip39.mnemonicToSeedHex(mnemonic, passphrase));
462
        } catch (e) {
463
            return q.reject(e);
464
        }
465
    }
466
};
467
468
APIClient.prototype.resolvePrimaryPrivateKeyFromOptions = function(options, cb) {
469
    var self = this;
470
471
    var deferred = q.defer();
472
    deferred.promise.nodeify(cb);
473
474
    try {
475
        // avoid conflicting options
476
        if (options.passphrase && options.password) {
477
            throw new blocktrail.WalletCreateError("Can't specify passphrase and password");
478
        }
479
        // normalize passphrase/password
480
        options.passphrase = options.passphrase || options.password;
481
        delete options.password;
482
483
        // avoid conflicting options
484
        if (options.primaryMnemonic && options.primarySeed) {
485
            throw new blocktrail.WalletInitError("Can only specify one of; Primary Mnemonic or Primary Seed");
486
        }
487
488
        // avoid deprecated options
489
        if (options.primaryPrivateKey) {
490
            throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
491
        }
492
493
        // make sure we have at least one thing to use
494
        if (!options.primaryMnemonic && !options.primarySeed) {
495
            throw new blocktrail.WalletInitError("Need to specify at least one of; Primary Mnemonic or Primary Seed");
496
        }
497
498
        if (options.primarySeed) {
499
            self.primarySeed = options.primarySeed;
500
            options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(self.primarySeed, self.network);
501
            deferred.resolve(options);
502
        } else {
503
            if (!options.passphrase) {
504
                throw new blocktrail.WalletInitError("Can't init wallet with Primary Mnemonic without a passphrase");
505
            }
506
507
            self.mnemonicToSeedHex(options.primaryMnemonic, options.passphrase)
508
                .then(function(seedHex) {
509
                    try {
510
                        options.primarySeed = new Buffer(seedHex, 'hex');
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
511
                        options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.primarySeed, self.network);
512
                        deferred.resolve(options);
513
                    } catch (e) {
514
                        deferred.reject(e);
515
                    }
516
                }, function(e) {
517
                    deferred.reject(e);
518
                });
519
        }
520
    } catch (e) {
521
        deferred.reject(e);
522
    }
523
524
    return deferred.promise;
525
};
526
527
APIClient.prototype.resolveBackupPublicKeyFromOptions = function(options, cb) {
528
    var self = this;
529
530
    var deferred = q.defer();
531
    deferred.promise.nodeify(cb);
532
533
    try {
534
        // avoid conflicting options
535
        if (options.backupMnemonic && options.backupPublicKey) {
536
            throw new blocktrail.WalletInitError("Can only specify one of; Backup Mnemonic or Backup PublicKey");
537
        }
538
539
        // make sure we have at least one thing to use
540
        if (!options.backupMnemonic && !options.backupPublicKey) {
541
            throw new blocktrail.WalletInitError("Need to specify at least one of; Backup Mnemonic or Backup PublicKey");
542
        }
543
544
        if (options.backupPublicKey) {
545
            if (options.backupPublicKey instanceof bitcoin.HDNode) {
546
                deferred.resolve(options);
547
            } else {
548
                options.backupPublicKey = bitcoin.HDNode.fromBase58(options.backupPublicKey, self.network);
549
                deferred.resolve(options);
550
            }
551
        } else {
552
            self.mnemonicToPrivateKey(options.backupMnemonic, "").then(function(backupPrivateKey) {
553
                options.backupPublicKey = backupPrivateKey.neutered();
554
                deferred.resolve(options);
555
            }, function(e) {
556
                deferred.reject(e);
557
            });
558
        }
559
    } catch (e) {
560
        deferred.reject(e);
561
    }
562
563
    return deferred.promise;
564
};
565
566
APIClient.prototype.debugAuth = function(cb) {
567
    var self = this;
568
569
    return self.dataClient.get("/debug/http-signature", null, true, cb);
570
};
571
572
/**
573
 * get a single address
574
 *
575
 * @param address      string       address hash
576
 * @param [cb]          function    callback function to call when request is complete
577
 * @return q.Promise
578
 */
579
APIClient.prototype.address = function(address, cb) {
580
    var self = this;
581
582
    return callbackify(self.dataClient.get(self.converter.getUrlForAddress(address), null)
583
        .then(function(data) {
584
            return self.converter.handleErros(self, data);
585
        })
586
        .then(function(data) {
587
            if (data === null) {
588
                return data;
589
            } else {
590
                return self.converter.convertAddress(data);
591
            }
592
        }), cb);
593
};
594
595
APIClient.prototype.addresses = function(addresses, cb) {
596
    var self = this;
597
598
    return callbackify(self.dataClient.post("/address", null, {"addresses": addresses}), cb);
599
};
600
601
602
/**
603
 * get all transactions for an address (paginated)
604
 *
605
 * @param address       string      address hash
606
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
607
 * @param [cb]          function    callback function to call when request is complete
608
 * @return q.Promise
609
 */
610
APIClient.prototype.addressTransactions = function(address, params, cb) {
611
612
    var self = this;
613
614
    if (typeof params === "function") {
615
        cb = params;
616
        params = null;
617
    }
618
619
    return callbackify(self.dataClient.get(self.converter.getUrlForAddressTransactions(address), self.converter.paginationParams(params))
620
        .then(function(data) {
621
            return self.converter.handleErros(self, data);
622
        })
623
        .then(function(data) {
624
            return data.data === null ? data : self.converter.convertAddressTxs(data);
625
        }), cb);
626
};
627
628
/**
629
 * get all transactions for a batch of addresses (paginated)
630
 *
631
 * @param addresses     array       address hashes
632
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
633
 * @param [cb]          function    callback function to call when request is complete
634
 * @return q.Promise
635
 */
636
APIClient.prototype.batchAddressHasTransactions = function(addresses, params, cb) {
637
    var self = this;
638
639
    if (typeof params === "function") {
640
        cb = params;
641
        params = null;
642
    }
643
644
    var deferred = q.defer();
645
646
    var promise = q();
647
648
    addresses.forEach(function(address) {
649
        promise = promise.then(function(hasTxs) {
650
            if (hasTxs) {
651
                return hasTxs;
652
            }
653
654
            return q(address)
655
                .then(function(address) {
656
                    console.log(address);
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
657
                    return self.addressTransactions(address, params)
658
                        .then(function(res) {
659
                            // err_no=1 is no txs found
660
                            if (res.err_no === 1) {
661
                                return false;
662
                            } else if (res.err_no) {
663
                                throw new Error("err: " + res.err_msg);
664
                            }
665
666
                            return res.data && res.data.length > 0;
667
                        });
668
                });
669
        });
670
    });
671
672
    promise.then(function(hasTxs) {
673
        deferred.resolve({has_transactions: hasTxs});
674
    }, function(err) {
675
        deferred.reject(err);
676
    });
677
678
    return callbackify(deferred.promise, cb);
679
};
680
681
/**
682
 * get all unconfirmed transactions for an address (paginated)
683
 *
684
 * @param address       string      address hash
685
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
686
 * @param [cb]          function    callback function to call when request is complete
687
 * @return q.Promise
688
 */
689
APIClient.prototype.addressUnconfirmedTransactions = function(address, params, cb) {
690
    var self = this;
691
692
    if (typeof params === "function") {
693
        cb = params;
694
        params = null;
695
    }
696
697
    return callbackify(self.dataClient.get(self.converter.getUrlForAddressTransactions(address), self.converter.paginationParams(params))
698
        .then(function(data) {
699
            return self.converter.handleErros(self, data);
700
        })
701
        .then(function(data) {
702
            if (data.data === null) {
703
                return data;
704
            }
705
706
            var res = self.converter.convertAddressTxs(data);
707
            res.data = res.data.filter(function(tx) {
708
                return !tx.confirmations;
709
            });
710
711
            return res;
712
        }), cb);
713
};
714
715
/**
716
 * get all unspent outputs for an address (paginated)
717
 *
718
 * @param address       string      address hash
719
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
720
 * @param [cb]          function    callback function to call when request is complete
721
 * @return q.Promise
722
 */
723
APIClient.prototype.addressUnspentOutputs = function(address, params, cb) {
724
    var self = this;
725
726
    if (typeof params === "function") {
727
        cb = params;
728
        params = null;
729
    }
730
731
    return callbackify(self.dataClient.get(self.converter.getUrlForAddressUnspent(address), self.converter.paginationParams(params))
732
        .then(function(data) {
733
            return self.converter.handleErros(self, data);
734
        })
735
        .then(function(data) {
736
            return data.data === null ? data : self.converter.convertAddressUnspentOutputs(data, address);
737
        }), cb);
738
};
739
740
/**
741
 * get all unspent outputs for a batch of addresses (paginated)
742
 *
743
 * @param addresses     array       address hashes
744
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
745
 * @param [cb]          function    callback function to call when request is complete
746
 * @return q.Promise
747
 */
748
APIClient.prototype.batchAddressUnspentOutputs = function(addresses, params, cb) {
749
    var self = this;
750
751
    if (self.converter instanceof BtccomConverter) {
752
        return callbackify(self.dataClient.get(self.converter.getUrlForBatchAddressUnspent(addresses), self.converter.paginationParams(params))
753
            .then(function(data) {
754
                return self.converter.handleErros(self, data);
755
            })
756
            .then(function(data) {
757
                return data.data === null ? data : self.converter.convertBatchAddressUnspentOutputs(data);
758
            }), cb);
759
    } else {
760
761
        if (typeof params === "function") {
762
            cb = params;
763
            params = null;
764
        }
765
766
        return callbackify(self.dataClient.post("/address/unspent-outputs", params, {"addresses": addresses}), cb);
767
    }
768
};
769
770
/**
771
 * verify ownership of an address
772
 *
773
 * @param address       string      address hash
774
 * @param signature     string      a signed message (the address hash) using the private key of the address
775
 * @param [cb]          function    callback function to call when request is complete
776
 * @return q.Promise
777
 */
778
APIClient.prototype.verifyAddress = function(address, signature, cb) {
779
    var self = this;
780
781
    return self.verifyMessage(address, address, signature, cb);
782
};
783
784
/**
785
 *
786
 * get all blocks (paginated)
787
 * ASK
788
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
789
 * @param [cb]          function    callback function to call when request is complete
790
 * @return q.Promise
791
 */
792
APIClient.prototype.allBlocks = function(params, cb) {
793
    var self = this;
794
795
    if (typeof params === "function") {
796
        cb = params;
797
        params = null;
798
    }
799
800
    return callbackify(self.dataClient.get(self.converter.getUrlForAllBlocks(), self.converter.paginationParams(params))
801
            .then(function(data) {
802
                return self.converter.handleErros(self, data);
803
            })
804
            .then(function(data) {
805
                return data.data === null ? data : self.converter.convertBlocks(data);
806
            }), cb);
807
};
808
809
/**
810
 * get a block
811
 *
812
 * @param block         string|int  a block hash or a block height
813
 * @param [cb]          function    callback function to call when request is complete
814
 * @return q.Promise
815
 */
816
APIClient.prototype.block = function(block, cb) {
817
    var self = this;
818
819
    return callbackify(self.dataClient.get(self.converter.getUrlForBlock(block), null)
820
        .then(function(data) {
821
            return self.converter.handleErros(self, data);
822
        })
823
        .then(function(data) {
824
            return data.data === null ? data : self.converter.convertBlock(data.data);
825
        }), cb);
826
};
827
828
/**
829
 * get the latest block
830
 *
831
 * @param [cb]          function    callback function to call when request is complete
832
 * @return q.Promise
833
 */
834
APIClient.prototype.blockLatest = function(cb) {
835
    var self = this;
836
837
    return callbackify(self.dataClient.get(self.converter.getUrlForBlock("latest"), null)
838
        .then(function(data) {
839
            return self.converter.handleErros(self, data);
840
        })
841
        .then(function(data) {
842
            return data.data === null ? data : self.converter.convertBlock(data.data);
843
        }), cb);
844
};
845
846
/**
847
 * get all transactions for a block (paginated)
848
 *
849
 * @param block         string|int  a block hash or a block height
850
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
851
 * @param [cb]          function    callback function to call when request is complete
852
 * @return q.Promise
853
 */
854
APIClient.prototype.blockTransactions = function(block, params, cb) {
855
    var self = this;
856
857
    if (typeof params === "function") {
858
        cb = params;
859
        params = null;
860
    }
861
862
    return callbackify(self.dataClient.get(self.converter.getUrlForBlockTransaction(block), self.converter.paginationParams(params))
863
        .then(function(data) {
864
            return self.converter.handleErros(self, data);
865
        })
866
        .then(function(data) {
867
            return data.data ===  null ? data : self.converter.convertBlockTxs(data);
868
        }), cb);
869
};
870
871
/**
872
 * get a single transaction
873
 *
874
 * @param tx            string      transaction hash
875
 * @param [cb]          function    callback function to call when request is complete
876
 * @return q.Promise
877
 */
878
APIClient.prototype.transaction = function(tx, cb) {
879
    var self = this;
880
881
    return callbackify(self.dataClient.get(self.converter.getUrlForTransaction(tx), null)
882
        .then(function(data) {
883
            return self.converter.handleErros(self, data);
884
        })
885
        .then(function(data) {
886
            if (data.data === null) {
887
                return data;
888
            } else {
889
                // for BTC.com API we need to fetch the raw hex from the BTC.com explorer endpoint
890
                if (self.converter instanceof BtccomConverter) {
891
                    return self.dataClient.get(self.converter.getUrlForRawTransaction(tx), null)
892
                        .then(function(rawData) {
893
                            return [data, rawData.data];
894
                        })
895
                        .then(function(dataAndTx) {
896
                            if (dataAndTx !== null) {
897
                                var data = dataAndTx[0];
898
                                var rawTx = dataAndTx[1];
899
                                return self.converter.convertTx(data, rawTx);
900
                            } else {
901
                                return dataAndTx;
902
                            }
903
                        });
904
                } else {
905
                    return self.converter.convertTx(data);
906
                }
907
            }
908
        }), cb);
909
};
910
911
/**
912
 * get a batch of transactions
913
 *
914
 * @param txs           string[]    list of transaction hashes (txId)
915
 * @param [cb]          function    callback function to call when request is complete
916
 * @return q.Promise
917
 */
918
APIClient.prototype.transactions = function(txs, cb) {
919
    var self = this;
920
921
    if (self.converter instanceof BtccomConverter) {
922
        return callbackify(self.dataClient.get(self.converter.getUrlForTransactions(txs), null)
923
            .then(function(data) {
924
                return self.converter.handleErros(self, data);
925
            })
926
            .then(function(data) {
927
                if (data.data === null) {
928
                    return data;
929
                } else {
930
                    return self.converter.convertTxs(data);
931
                }
932
            }), cb);
933
    } else {
934
        return callbackify(self.dataClient.post("/transactions", null, txs, null, false), cb);
935
    }
936
};
937
938
/**
939
 * get a paginated list of all webhooks associated with the api user
940
 *
941
 * @param [params]      object      pagination: {page: 1, limit: 20}
942
 * @param [cb]          function    callback function to call when request is complete
943
 * @return q.Promise
944
 */
945
APIClient.prototype.allWebhooks = function(params, cb) {
946
    var self = this;
947
948
    if (typeof params === "function") {
949
        cb = params;
950
        params = null;
951
    }
952
953
    return self.blocktrailClient.get("/webhooks", params, cb);
954
};
955
956
/**
957
 * create a new webhook
958
 *
959
 * @param url           string      the url to receive the webhook events
960
 * @param [identifier]  string      a unique identifier associated with the webhook
961
 * @param [cb]          function    callback function to call when request is complete
962
 * @return q.Promise
963
 */
964
APIClient.prototype.setupWebhook = function(url, identifier, cb) {
965
    var self = this;
966
967
    if (typeof identifier === "function") {
968
        //mimic function overloading
969
        cb = identifier;
970
        identifier = null;
971
    }
972
973
    return self.blocktrailClient.post("/webhook", null, {url: url, identifier: identifier}, cb);
974
};
975
976
/**
977
 * Converts a cash address to the legacy (base58) format
978
 * @param {string} input
979
 * @returns {string}
980
 */
981
APIClient.prototype.getLegacyBitcoinCashAddress = function(input) {
982
    if (this.network === bitcoin.networks.bitcoincash ||
983
        this.network === bitcoin.networks.bitcoincashtestnet ||
984
        this.network === bitcoin.networks.bitcoincashregtest) {
985
        var address;
986
        try {
987
            bitcoin.address.fromBase58Check(input, this.network);
988
            return input;
989
        } catch (e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
990
991
        address = bitcoin.address.fromCashAddress(input, this.network);
992
        var prefix;
993
        if (address.version === bitcoin.script.types.P2PKH) {
994
            prefix = this.network.pubKeyHash;
995
        } else if (address.version === bitcoin.script.types.P2SH) {
996
            prefix = this.network.scriptHash;
997
        } else {
998
            throw new Error("Unsupported address type");
999
        }
1000
1001
        return bitcoin.address.toBase58Check(address.hash, prefix);
1002
    }
1003
1004
    throw new Error("Cash addresses only work on bitcoin cash");
1005
};
1006
1007
/**
1008
 * Converts a legacy bitcoin to the new cashaddr format
1009
 * @param {string} input
1010
 * @returns {string}
1011
 */
1012
APIClient.prototype.getCashAddressFromLegacyAddress = function(input) {
1013
    if (this.network === bitcoin.networks.bitcoincash ||
1014
        this.network === bitcoin.networks.bitcoincashtestnet ||
1015
        this.network === bitcoin.networks.bitcoincashregtest
1016
    ) {
1017
        var address;
1018
        try {
1019
            bitcoin.address.fromCashAddress(input, this.network);
1020
            return input;
1021
        } catch (e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
1022
1023
        address = bitcoin.address.fromBase58Check(input, this.network);
1024
        var scriptType;
1025
        if (address.version === this.network.pubKeyHash) {
1026
            scriptType = bitcoin.script.types.P2PKH;
1027
        } else if (address.version === this.network.scriptHash) {
1028
            scriptType = bitcoin.script.types.P2SH;
1029
        } else {
1030
            throw new Error("Unsupported address type");
1031
        }
1032
1033
        return bitcoin.address.toCashAddress(address.hash, scriptType, this.network.cashAddrPrefix);
1034
    }
1035
1036
    throw new Error("Cash addresses only work on bitcoin cash");
1037
};
1038
1039
/**
1040
 * get an existing webhook by it's identifier
1041
 *
1042
 * @param identifier    string      the unique identifier of the webhook to get
1043
 * @param [cb]          function    callback function to call when request is complete
1044
 * @return q.Promise
1045
 */
1046
APIClient.prototype.getWebhook = function(identifier, cb) {
1047
    var self = this;
1048
1049
    return self.blocktrailClient.get("/webhook/" + identifier, null, cb);
1050
};
1051
1052
/**
1053
 * update an existing webhook
1054
 *
1055
 * @param identifier    string      the unique identifier of the webhook
1056
 * @param webhookData   object      the data to update: {identifier: newIdentifier, url:newUrl}
1057
 * @param [cb]          function    callback function to call when request is complete
1058
 * @return q.Promise
1059
 */
1060
APIClient.prototype.updateWebhook = function(identifier, webhookData, cb) {
1061
    var self = this;
1062
1063
    return self.blocktrailClient.put("/webhook/" + identifier, null, webhookData, cb);
1064
};
1065
1066
/**
1067
 * deletes an existing webhook and any event subscriptions associated with it
1068
 *
1069
 * @param identifier    string      the unique identifier of the webhook
1070
 * @param [cb]          function    callback function to call when request is complete
1071
 * @return q.Promise
1072
 */
1073
APIClient.prototype.deleteWebhook = function(identifier, cb) {
1074
    var self = this;
1075
1076
    return self.blocktrailClient.delete("/webhook/" + identifier, null, null, cb);
1077
};
1078
1079
/**
1080
 * get a paginated list of all the events a webhook is subscribed to
1081
 *
1082
 * @param identifier    string      the unique identifier of the webhook
1083
 * @param [params]      object      pagination: {page: 1, limit: 20}
1084
 * @param [cb]          function    callback function to call when request is complete
1085
 * @return q.Promise
1086
 */
1087
APIClient.prototype.getWebhookEvents = function(identifier, params, cb) {
1088
    var self = this;
1089
1090
    if (typeof params === "function") {
1091
        cb = params;
1092
        params = null;
1093
    }
1094
1095
    return self.blocktrailClient.get("/webhook/" + identifier + "/events", params, cb);
1096
};
1097
1098
/**
1099
 * subscribes a webhook to transaction events for a particular transaction
1100
 *
1101
 * @param identifier    string      the unique identifier of the webhook
1102
 * @param transaction   string      the transaction hash
1103
 * @param confirmations integer     the amount of confirmations to send
1104
 * @param [cb]          function    callback function to call when request is complete
1105
 * @return q.Promise
1106
 */
1107
APIClient.prototype.subscribeTransaction = function(identifier, transaction, confirmations, cb) {
1108
    var self = this;
1109
    var postData = {
1110
        'event_type': 'transaction',
1111
        'transaction': transaction,
1112
        'confirmations': confirmations
1113
    };
1114
1115
    return self.blocktrailClient.post("/webhook/" + identifier + "/events", null, postData, cb);
1116
};
1117
1118
/**
1119
 * subscribes a webhook to transaction events on a particular address
1120
 *
1121
 * @param identifier    string      the unique identifier of the webhook
1122
 * @param address       string      the address hash
1123
 * @param confirmations integer     the amount of confirmations to send
1124
 * @param [cb]          function    callback function to call when request is complete
1125
 * @return q.Promise
1126
 */
1127
APIClient.prototype.subscribeAddressTransactions = function(identifier, address, confirmations, cb) {
1128
    var self = this;
1129
    var postData = {
1130
        'event_type': 'address-transactions',
1131
        'address': address,
1132
        'confirmations': confirmations
1133
    };
1134
1135
    return self.blocktrailClient.post("/webhook/" + identifier + "/events", null, postData, cb);
1136
};
1137
1138
/**
1139
 * batch subscribes a webhook to multiple transaction events
1140
 *
1141
 * @param  identifier   string      the unique identifier of the webhook
1142
 * @param  batchData    array       An array of objects containing batch event data:
1143
 *                                  {address : 'address', confirmations : 'confirmations']
1144
 *                                  where address is the address to subscribe to and confirmations (optional) is the amount of confirmations to send
1145
 * @param [cb]          function    callback function to call when request is complete
1146
 * @return q.Promise
1147
 */
1148
APIClient.prototype.batchSubscribeAddressTransactions = function(identifier, batchData, cb) {
1149
    var self = this;
1150
    batchData.forEach(function(record) {
1151
        record.event_type = 'address-transactions';
1152
    });
1153
1154
    return self.blocktrailClient.post("/webhook/" + identifier + "/events/batch", null, batchData, cb);
1155
};
1156
1157
/**
1158
 * subscribes a webhook to a new block event
1159
 *
1160
 * @param identifier    string      the unique identifier of the webhook
1161
 * @param [cb]          function    callback function to call when request is complete
1162
 * @return q.Promise
1163
 */
1164
APIClient.prototype.subscribeNewBlocks = function(identifier, cb) {
1165
    var self = this;
1166
    var postData = {
1167
        'event_type': 'block'
1168
    };
1169
1170
    return self.blocktrailClient.post("/webhook/" + identifier + "/events", null, postData, cb);
1171
};
1172
1173
/**
1174
 * removes an address transaction event subscription from a webhook
1175
 *
1176
 * @param identifier    string      the unique identifier of the webhook
1177
 * @param address       string      the address hash
1178
 * @param [cb]          function    callback function to call when request is complete
1179
 * @return q.Promise
1180
 */
1181
APIClient.prototype.unsubscribeAddressTransactions = function(identifier, address, cb) {
1182
    var self = this;
1183
1184
    return self.blocktrailClient.delete("/webhook/" + identifier + "/address-transactions/" + address, null, null, cb);
1185
};
1186
1187
/**
1188
 * removes an transaction event subscription from a webhook
1189
 *
1190
 * @param identifier    string      the unique identifier of the webhook
1191
 * @param transaction   string      the transaction hash
1192
 * @param [cb]          function    callback function to call when request is complete
1193
 * @return q.Promise
1194
 */
1195
APIClient.prototype.unsubscribeTransaction = function(identifier, transaction, cb) {
1196
    var self = this;
1197
1198
    return self.blocktrailClient.delete("/webhook/" + identifier + "/transaction/" + transaction, null, null, cb);
1199
};
1200
1201
/**
1202
 * removes a block event subscription from a webhook
1203
 *
1204
 * @param identifier    string      the unique identifier of the webhook
1205
 * @param [cb]          function    callback function to call when request is complete
1206
 * @return q.Promise
1207
 */
1208
APIClient.prototype.unsubscribeNewBlocks = function(identifier, cb) {
1209
    var self = this;
1210
1211
    return self.blocktrailClient.delete("/webhook/" + identifier + "/block", null, null, cb);
1212
};
1213
1214
/**
1215
 * initialize an existing wallet
1216
 *
1217
 * Either takes two argument:
1218
 * @param options       object      {}
1219
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
1220
 *
1221
 * Or takes three arguments (old, deprecated syntax):
1222
 * @param identifier    string      the wallet identifier to be initialized
0 ignored issues
show
Documentation introduced by
The parameter identifier does not exist. Did you maybe forget to remove this comment?
Loading history...
1223
 * @param passphrase    string      the password to decrypt the mnemonic with
0 ignored issues
show
Documentation introduced by
The parameter passphrase does not exist. Did you maybe forget to remove this comment?
Loading history...
1224
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 1219. The second definition is ignored.
Loading history...
1225
 *
1226
 * @returns {q.Promise}
1227
 */
1228
APIClient.prototype.initWallet = function(options, cb) {
1229
    var self = this;
1230
1231
    if (typeof options !== "object") {
1232
        // get the old-style arguments
1233
        options = {
1234
            identifier: arguments[0],
1235
            passphrase: arguments[1]
1236
        };
1237
1238
        cb = arguments[2];
1239
    }
1240
1241
    if (options.check_backup_key) {
1242
        if (typeof options.check_backup_key !== "string") {
1243
            throw new Error("Invalid input, must provide the backup key as a string (the xpub)");
1244
        }
1245
    }
1246
1247
    var deferred = q.defer();
1248
    deferred.promise.spreadNodeify(cb);
1249
1250
    var identifier = options.identifier;
1251
1252
    if (!identifier) {
1253
        deferred.reject(new blocktrail.WalletInitError("Identifier is required"));
1254
        return deferred.promise;
1255
    }
1256
1257
    deferred.resolve(self.blocktrailClient.get("/wallet/" + identifier, null, true).then(function(result) {
1258
        var keyIndex = options.keyIndex || result.key_index;
1259
1260
        options.walletVersion = result.wallet_version;
1261
1262
        if (options.check_backup_key) {
1263
            if (options.check_backup_key !== result.backup_public_key[0]) {
1264
                throw new Error("Backup key returned from server didn't match our own copy");
1265
            }
1266
        }
1267
        var backupPublicKey = bitcoin.HDNode.fromBase58(result.backup_public_key[0], self.network);
1268
        var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1269
            return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1270
        });
1271
        var primaryPublicKeys = _.mapValues(result.primary_public_keys, function(primaryPublicKey) {
1272
            return bitcoin.HDNode.fromBase58(primaryPublicKey[0], self.network);
1273
        });
1274
1275
        // initialize wallet
1276
        var wallet = new Wallet(
1277
            self,
1278
            identifier,
1279
            options.walletVersion,
1280
            result.primary_mnemonic,
1281
            result.encrypted_primary_seed,
1282
            result.encrypted_secret,
1283
            primaryPublicKeys,
1284
            backupPublicKey,
1285
            blocktrailPublicKeys,
1286
            keyIndex,
1287
            result.segwit || 0,
1288
            self.testnet,
1289
            self.regtest,
1290
            result.checksum,
1291
            result.upgrade_key_index,
1292
            options.useCashAddress,
1293
            options.bypassNewAddressCheck
1294
        );
1295
1296
        wallet.recoverySecret = result.recovery_secret;
1297
1298
        if (!options.readOnly) {
1299
            return wallet.unlock(options).then(function() {
1300
                return wallet;
1301
            });
1302
        } else {
1303
            return wallet;
1304
        }
1305
    }));
1306
1307
    return deferred.promise;
1308
};
1309
1310
APIClient.CREATE_WALLET_PROGRESS_START = 0;
1311
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET = 4;
1312
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY = 5;
1313
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY = 6;
1314
APIClient.CREATE_WALLET_PROGRESS_PRIMARY = 10;
1315
APIClient.CREATE_WALLET_PROGRESS_BACKUP = 20;
1316
APIClient.CREATE_WALLET_PROGRESS_SUBMIT = 30;
1317
APIClient.CREATE_WALLET_PROGRESS_INIT = 40;
1318
APIClient.CREATE_WALLET_PROGRESS_DONE = 100;
1319
1320
/**
1321
 * create a new wallet
1322
 *   - will generate a new primary seed and backup seed
1323
 *
1324
 * Either takes two argument:
1325
 * @param options       object      {}
1326
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
1327
 *
1328
 * For v1 wallets (explicitly specify options.walletVersion=v1):
1329
 * @param options       object      {}
0 ignored issues
show
Documentation introduced by
The parameter options has already been documented on line 1325. The second definition is ignored.
Loading history...
1330
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 1326. The second definition is ignored.
Loading history...
1331
 *
1332
 * Or takes four arguments (old, deprecated syntax):
1333
 * @param identifier    string      the wallet identifier to be initialized
0 ignored issues
show
Documentation introduced by
The parameter identifier does not exist. Did you maybe forget to remove this comment?
Loading history...
1334
 * @param passphrase    string      the password to decrypt the mnemonic with
0 ignored issues
show
Documentation introduced by
The parameter passphrase does not exist. Did you maybe forget to remove this comment?
Loading history...
1335
 * @param keyIndex      int         override for the blocktrail cosign key to use (for development purposes)
0 ignored issues
show
Documentation introduced by
The parameter keyIndex does not exist. Did you maybe forget to remove this comment?
Loading history...
1336
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 1326. The second definition is ignored.
Loading history...
1337
 * @returns {q.Promise}
1338
 */
1339
APIClient.prototype.createNewWallet = function(options, cb) {
1340
    /* jshint -W071, -W074 */
1341
1342
    var self = this;
1343
1344
    if (typeof options !== "object") {
1345
        // get the old-style arguments
1346
        var identifier = arguments[0];
1347
        var passphrase = arguments[1];
1348
        var keyIndex = arguments[2];
1349
        cb = arguments[3];
1350
1351
        // keyIndex is optional
1352
        if (typeof keyIndex === "function") {
1353
            cb = keyIndex;
1354
            keyIndex = null;
1355
        }
1356
1357
        options = {
1358
            identifier: identifier,
1359
            passphrase: passphrase,
1360
            keyIndex: keyIndex
1361
        };
1362
    }
1363
1364
    // default to v3
1365
    options.walletVersion = options.walletVersion || Wallet.WALLET_VERSION_V3;
1366
1367
    var deferred = q.defer();
1368
    deferred.promise.spreadNodeify(cb);
1369
1370
    q.nextTick(function() {
1371
        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_START);
1372
1373
        options.keyIndex = options.keyIndex || 0;
1374
        options.passphrase = options.passphrase || options.password;
1375
        delete options.password;
1376
1377
        if (!options.identifier) {
1378
            deferred.reject(new blocktrail.WalletCreateError("Identifier is required"));
1379
            return deferred.promise;
1380
        }
1381
1382
        if (options.walletVersion === Wallet.WALLET_VERSION_V1) {
1383
            self._createNewWalletV1(options)
1384
                .progress(function(p) { deferred.notify(p); })
1385
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1386
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1387
        } else if (options.walletVersion === Wallet.WALLET_VERSION_V2) {
1388
            self._createNewWalletV2(options)
1389
                .progress(function(p) { deferred.notify(p); })
1390
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1391
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1392
        } else if (options.walletVersion === Wallet.WALLET_VERSION_V3) {
1393
            self._createNewWalletV3(options)
1394
                .progress(function(p) { deferred.notify(p); })
1395
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1396
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1397
        } else {
1398
            deferred.reject(new blocktrail.WalletCreateError("Invalid wallet version!"));
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1399
        }
1400
    });
1401
1402
    return deferred.promise;
1403
};
1404
1405
APIClient.prototype._createNewWalletV1 = function(options) {
1406
    var self = this;
1407
1408
    var deferred = q.defer();
1409
1410
    q.nextTick(function() {
1411
1412
        if (!options.primaryMnemonic && !options.primarySeed) {
1413
            if (!options.passphrase && !options.password) {
1414
                deferred.reject(new blocktrail.WalletCreateError("Can't generate Primary Mnemonic without a passphrase"));
1415
                return deferred.promise;
1416
            } else {
1417
                options.primaryMnemonic = bip39.generateMnemonic(Wallet.WALLET_ENTROPY_BITS);
1418
                if (options.storePrimaryMnemonic !== false) {
1419
                    options.storePrimaryMnemonic = true;
1420
                }
1421
            }
1422
        }
1423
1424
        if (!options.backupMnemonic && !options.backupPublicKey) {
1425
            options.backupMnemonic = bip39.generateMnemonic(Wallet.WALLET_ENTROPY_BITS);
1426
        }
1427
1428
        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_PRIMARY);
1429
1430
        self.resolvePrimaryPrivateKeyFromOptions(options)
1431
            .then(function(options) {
1432
                deferred.notify(APIClient.CREATE_WALLET_PROGRESS_BACKUP);
1433
1434
                return self.resolveBackupPublicKeyFromOptions(options)
1435
                    .then(function(options) {
1436
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_SUBMIT);
1437
1438
                        // create a checksum of our private key which we'll later use to verify we used the right password
1439
                        var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1440
                        var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1441
                        var keyIndex = options.keyIndex;
1442
1443
                        var primaryPublicKey = options.primaryPrivateKey.deriveHardened(keyIndex).neutered();
1444
1445
                        // send the public keys to the server to store them
1446
                        //  and the mnemonic, which is safe because it's useless without the password
1447
                        return self.storeNewWalletV1(
1448
                            options.identifier,
1449
                            [primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1450
                            [options.backupPublicKey.toBase58(), "M"],
1451
                            options.storePrimaryMnemonic ? options.primaryMnemonic : false,
1452
                            checksum,
1453
                            keyIndex,
1454
                            options.segwit || null
1455
                        )
1456
                            .then(function(result) {
1457
                                deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1458
1459
                                var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1460
                                    return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1461
                                });
1462
1463
                                var wallet = new Wallet(
1464
                                    self,
1465
                                    options.identifier,
1466
                                    Wallet.WALLET_VERSION_V1,
1467
                                    options.primaryMnemonic,
1468
                                    null,
1469
                                    null,
1470
                                    {keyIndex: primaryPublicKey},
1471
                                    options.backupPublicKey,
1472
                                    blocktrailPublicKeys,
1473
                                    keyIndex,
1474
                                    result.segwit || 0,
1475
                                    self.testnet,
1476
                                    self.regtest,
1477
                                    checksum,
1478
                                    result.upgrade_key_index,
1479
                                    options.useCashAddress,
1480
                                    options.bypassNewAddressCheck
1481
                                );
1482
1483
                                return wallet.unlock({
1484
                                    walletVersion: Wallet.WALLET_VERSION_V1,
1485
                                    passphrase: options.passphrase,
1486
                                    primarySeed: options.primarySeed,
1487
                                    primaryMnemonic: null // explicit null
1488
                                }).then(function() {
1489
                                    deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1490
                                    return [
1491
                                        wallet,
1492
                                        {
1493
                                            walletVersion: wallet.walletVersion,
1494
                                            primaryMnemonic: options.primaryMnemonic,
1495
                                            backupMnemonic: options.backupMnemonic,
1496
                                            blocktrailPublicKeys: blocktrailPublicKeys
1497
                                        }
1498
                                    ];
1499
                                });
1500
                            });
1501
                    }
1502
                );
1503
            })
1504
            .then(
1505
            function(r) {
1506
                deferred.resolve(r);
1507
            },
1508
            function(e) {
1509
                deferred.reject(e);
1510
            }
1511
        )
1512
        ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1513
    });
1514
1515
    return deferred.promise;
1516
};
1517
1518 View Code Duplication
APIClient.prototype._createNewWalletV2 = function(options) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1519
    var self = this;
1520
1521
    var deferred = q.defer();
1522
1523
    // avoid modifying passed options
1524
    options = _.merge({}, options);
1525
1526
    determineDataStorageV2_3(options)
1527
        .then(function(options) {
1528
            options.passphrase = options.passphrase || options.password;
1529
            delete options.password;
1530
1531
            // avoid deprecated options
1532
            if (options.primaryPrivateKey) {
1533
                throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
1534
            }
1535
1536
            // seed should be provided or generated
1537
            options.primarySeed = options.primarySeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
1538
1539
            return options;
1540
        })
1541
        .then(function(options) {
1542
            return produceEncryptedDataV2(options, deferred.notify.bind(deferred));
1543
        })
1544
        .then(function(options) {
1545
            return doRemainingWalletDataV2_3(options, self.network, deferred.notify.bind(deferred));
1546
        })
1547
        .then(function(options) {
1548
            // create a checksum of our private key which we'll later use to verify we used the right password
1549
            var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1550
            var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1551
            var keyIndex = options.keyIndex;
1552
1553
            // send the public keys and encrypted data to server
1554
            return self.storeNewWalletV2(
1555
                options.identifier,
1556
                [options.primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1557
                [options.backupPublicKey.toBase58(), "M"],
1558
                options.storeDataOnServer ? options.encryptedPrimarySeed : false,
1559
                options.storeDataOnServer ? options.encryptedSecret : false,
1560
                options.storeDataOnServer ? options.recoverySecret : false,
1561
                checksum,
1562
                keyIndex,
1563
                options.support_secret || null,
1564
                options.segwit || null
1565
            )
1566
                .then(
1567
                function(result) {
1568
                    deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1569
1570
                    var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1571
                        return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1572
                    });
1573
1574
                    var wallet = new Wallet(
1575
                        self,
1576
                        options.identifier,
1577
                        Wallet.WALLET_VERSION_V2,
1578
                        null,
1579
                        options.storeDataOnServer ? options.encryptedPrimarySeed : null,
1580
                        options.storeDataOnServer ? options.encryptedSecret : null,
1581
                        {keyIndex: options.primaryPublicKey},
1582
                        options.backupPublicKey,
1583
                        blocktrailPublicKeys,
1584
                        keyIndex,
1585
                        result.segwit || 0,
1586
                        self.testnet,
1587
                        self.regtest,
1588
                        checksum,
1589
                        result.upgrade_key_index,
1590
                        options.useCashAddress,
1591
                        options.bypassNewAddressCheck
1592
                    );
1593
1594
                    // pass along decrypted data to avoid extra work
1595
                    return wallet.unlock({
1596
                        walletVersion: Wallet.WALLET_VERSION_V2,
1597
                        passphrase: options.passphrase,
1598
                        primarySeed: options.primarySeed,
1599
                        secret: options.secret
1600
                    }).then(function() {
1601
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1602
                        return [
1603
                            wallet,
1604
                            {
1605
                                walletVersion: wallet.walletVersion,
1606
                                encryptedPrimarySeed: options.encryptedPrimarySeed ?
1607
                                    bip39.entropyToMnemonic(blocktrail.convert(options.encryptedPrimarySeed, 'base64', 'hex')) :
1608
                                    null,
1609
                                backupSeed: options.backupSeed ? bip39.entropyToMnemonic(options.backupSeed.toString('hex')) : null,
1610
                                recoveryEncryptedSecret: options.recoveryEncryptedSecret ?
1611
                                    bip39.entropyToMnemonic(blocktrail.convert(options.recoveryEncryptedSecret, 'base64', 'hex')) :
1612
                                    null,
1613
                                encryptedSecret: options.encryptedSecret ?
1614
                                    bip39.entropyToMnemonic(blocktrail.convert(options.encryptedSecret, 'base64', 'hex')) :
1615
                                    null,
1616
                                blocktrailPublicKeys: blocktrailPublicKeys
1617
                            }
1618
                        ];
1619
                    });
1620
                }
1621
            );
1622
        })
1623
       .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); });
1624
1625
    return deferred.promise;
1626
};
1627
1628 View Code Duplication
APIClient.prototype._createNewWalletV3 = function(options) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1629
    var self = this;
1630
1631
    var deferred = q.defer();
1632
1633
    // avoid modifying passed options
1634
    options = _.merge({}, options);
1635
1636
    determineDataStorageV2_3(options)
1637
        .then(function(options) {
1638
            options.passphrase = options.passphrase || options.password;
1639
            delete options.password;
1640
1641
            // avoid deprecated options
1642
            if (options.primaryPrivateKey) {
1643
                throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
1644
            }
1645
1646
            // seed should be provided or generated
1647
            options.primarySeed = options.primarySeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
1648
1649
            return options;
1650
        })
1651
        .then(function(options) {
1652
            return self.produceEncryptedDataV3(options, deferred.notify.bind(deferred));
1653
        })
1654
        .then(function(options) {
1655
            return doRemainingWalletDataV2_3(options, self.network, deferred.notify.bind(deferred));
1656
        })
1657
        .then(function(options) {
1658
            // create a checksum of our private key which we'll later use to verify we used the right password
1659
            var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1660
            var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1661
            var keyIndex = options.keyIndex;
1662
1663
            // send the public keys and encrypted data to server
1664
            return self.storeNewWalletV3(
1665
                options.identifier,
1666
                [options.primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1667
                [options.backupPublicKey.toBase58(), "M"],
1668
                options.storeDataOnServer ? options.encryptedPrimarySeed : false,
1669
                options.storeDataOnServer ? options.encryptedSecret : false,
1670
                options.storeDataOnServer ? options.recoverySecret : false,
1671
                checksum,
1672
                keyIndex,
1673
                options.support_secret || null,
1674
                options.segwit || null
1675
            )
1676
                .then(
1677
                    // result, deferred, self(apiclient)
1678
                    function(result) {
1679
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1680
1681
                        var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1682
                            return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1683
                        });
1684
1685
                        var wallet = new Wallet(
1686
                            self,
1687
                            options.identifier,
1688
                            Wallet.WALLET_VERSION_V3,
1689
                            null,
1690
                            options.storeDataOnServer ? options.encryptedPrimarySeed : null,
1691
                            options.storeDataOnServer ? options.encryptedSecret : null,
1692
                            {keyIndex: options.primaryPublicKey},
1693
                            options.backupPublicKey,
1694
                            blocktrailPublicKeys,
1695
                            keyIndex,
1696
                            result.segwit || 0,
1697
                            self.testnet,
1698
                            self.regtest,
1699
                            checksum,
1700
                            result.upgrade_key_index,
1701
                            options.useCashAddress,
1702
                            options.bypassNewAddressCheck
1703
                        );
1704
1705
                        // pass along decrypted data to avoid extra work
1706
                        return wallet.unlock({
1707
                            walletVersion: Wallet.WALLET_VERSION_V3,
1708
                            passphrase: options.passphrase,
1709
                            primarySeed: options.primarySeed,
1710
                            secret: options.secret
1711
                        }).then(function() {
1712
                            deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1713
                            return [
1714
                                wallet,
1715
                                {
1716
                                    walletVersion: wallet.walletVersion,
1717
                                    encryptedPrimarySeed: options.encryptedPrimarySeed ? EncryptionMnemonic.encode(options.encryptedPrimarySeed) : null,
1718
                                    backupSeed: options.backupSeed ? bip39.entropyToMnemonic(options.backupSeed) : null,
1719
                                    recoveryEncryptedSecret: options.recoveryEncryptedSecret ?
1720
                                        EncryptionMnemonic.encode(options.recoveryEncryptedSecret) : null,
1721
                                    encryptedSecret: options.encryptedSecret ? EncryptionMnemonic.encode(options.encryptedSecret) : null,
1722
                                    blocktrailPublicKeys: blocktrailPublicKeys
1723
                                }
1724
                            ];
1725
                        });
1726
                    }
1727
                );
1728
        })
1729
        .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); });
1730
1731
    return deferred.promise;
1732
};
1733
1734
function verifyPublicBip32Key(bip32Key, network) {
1735
    var hk = bitcoin.HDNode.fromBase58(bip32Key[0], network);
1736
    if (typeof hk.keyPair.d !== "undefined") {
1737
        throw new Error('BIP32Key contained private key material - abort');
1738
    }
1739
1740
    if (bip32Key[1].slice(0, 1) !== "M") {
1741
        throw new Error("BIP32Key contained non-public path - abort");
1742
    }
1743
}
1744
1745
function verifyPublicOnly(walletData, network) {
1746
    verifyPublicBip32Key(walletData.primary_public_key, network);
1747
    verifyPublicBip32Key(walletData.backup_public_key, network);
1748
}
1749
1750
/**
1751
 * create wallet using the API
1752
 *
1753
 * @param identifier            string      the wallet identifier to create
1754
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1755
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1756
 * @param primaryMnemonic       string      mnemonic to store
1757
 * @param checksum              string      checksum to store
1758
 * @param keyIndex              int         keyIndex that was used to create wallet
1759
 * @param segwit                bool
1760
 * @returns {q.Promise}
1761
 */
1762
APIClient.prototype.storeNewWalletV1 = function(identifier, primaryPublicKey, backupPublicKey, primaryMnemonic,
1763
                                                checksum, keyIndex, segwit) {
1764
    var self = this;
1765
1766
    var postData = {
1767
        identifier: identifier,
1768
        wallet_version: Wallet.WALLET_VERSION_V1,
1769
        primary_public_key: primaryPublicKey,
1770
        backup_public_key: backupPublicKey,
1771
        primary_mnemonic: primaryMnemonic,
1772
        checksum: checksum,
1773
        key_index: keyIndex,
1774
        segwit: segwit
1775
    };
1776
1777
    verifyPublicOnly(postData, self.network);
1778
1779
    return self.blocktrailClient.post("/wallet", null, postData);
1780
};
1781
1782
/**
1783
 * create wallet using the API
1784
 *
1785
 * @param identifier            string      the wallet identifier to create
1786
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1787
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1788
 * @param encryptedPrimarySeed  string      openssl format
1789
 * @param encryptedSecret       string      openssl format
1790
 * @param recoverySecret        string      openssl format
1791
 * @param checksum              string      checksum to store
1792
 * @param keyIndex              int         keyIndex that was used to create wallet
1793
 * @param supportSecret         string
1794
 * @param segwit                bool
1795
 * @returns {q.Promise}
1796
 */
1797
APIClient.prototype.storeNewWalletV2 = function(identifier, primaryPublicKey, backupPublicKey, encryptedPrimarySeed, encryptedSecret,
1798
                                                recoverySecret, checksum, keyIndex, supportSecret, segwit) {
1799
    var self = this;
1800
1801
    var postData = {
1802
        identifier: identifier,
1803
        wallet_version: Wallet.WALLET_VERSION_V2,
1804
        primary_public_key: primaryPublicKey,
1805
        backup_public_key: backupPublicKey,
1806
        encrypted_primary_seed: encryptedPrimarySeed,
1807
        encrypted_secret: encryptedSecret,
1808
        recovery_secret: recoverySecret,
1809
        checksum: checksum,
1810
        key_index: keyIndex,
1811
        support_secret: supportSecret || null,
1812
        segwit: segwit
1813
    };
1814
1815
    verifyPublicOnly(postData, self.network);
1816
1817
    return self.blocktrailClient.post("/wallet", null, postData);
1818
};
1819
1820
/**
1821
 * create wallet using the API
1822
 *
1823
 * @param identifier            string      the wallet identifier to create
1824
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1825
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1826
 * @param encryptedPrimarySeed  Buffer      buffer of ciphertext
1827
 * @param encryptedSecret       Buffer      buffer of ciphertext
1828
 * @param recoverySecret        Buffer      buffer of recovery secret
1829
 * @param checksum              string      checksum to store
1830
 * @param keyIndex              int         keyIndex that was used to create wallet
1831
 * @param supportSecret         string
1832
 * @param segwit                bool
1833
 * @returns {q.Promise}
1834
 */
1835
APIClient.prototype.storeNewWalletV3 = function(identifier, primaryPublicKey, backupPublicKey, encryptedPrimarySeed, encryptedSecret,
1836
                                                recoverySecret, checksum, keyIndex, supportSecret, segwit) {
1837
    var self = this;
1838
1839
    var postData = {
1840
        identifier: identifier,
1841
        wallet_version: Wallet.WALLET_VERSION_V3,
1842
        primary_public_key: primaryPublicKey,
1843
        backup_public_key: backupPublicKey,
1844
        encrypted_primary_seed: encryptedPrimarySeed.toString('base64'),
1845
        encrypted_secret: encryptedSecret.toString('base64'),
1846
        recovery_secret: recoverySecret.toString('hex'),
1847
        checksum: checksum,
1848
        key_index: keyIndex,
1849
        support_secret: supportSecret || null,
1850
        segwit: segwit
1851
    };
1852
1853
    verifyPublicOnly(postData, self.network);
1854
1855
    return self.blocktrailClient.post("/wallet", null, postData);
1856
};
1857
1858
/**
1859
 * create wallet using the API
1860
 *
1861
 * @param identifier            string      the wallet identifier to create
1862
 * @param postData              object
1863
 * @param [cb]                  function    callback(err, result)
1864
 * @returns {q.Promise}
1865
 */
1866
APIClient.prototype.updateWallet = function(identifier, postData, cb) {
1867
    var self = this;
1868
1869
    return self.blocktrailClient.post("/wallet/" + identifier, null, postData, cb);
1870
};
1871
1872
/**
1873
 * upgrade wallet to use a new account number
1874
 *  the account number specifies which blocktrail cosigning key is used
1875
 *
1876
 * @param identifier            string      the wallet identifier
1877
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1878
 * @param keyIndex              int         keyIndex that was used to create wallet
1879
 * @param [cb]                  function    callback(err, result)
1880
 * @returns {q.Promise}
1881
 */
1882
APIClient.prototype.upgradeKeyIndex = function(identifier, keyIndex, primaryPublicKey, cb) {
1883
    var self = this;
1884
1885
    return self.blocktrailClient.post("/wallet/" + identifier + "/upgrade", null, {
1886
        key_index: keyIndex,
1887
        primary_public_key: primaryPublicKey
1888
    }, cb);
1889
};
1890
1891
/**
1892
 * get the balance for the wallet
1893
 *
1894
 * @param identifier            string      the wallet identifier
1895
 * @param [cb]                  function    callback(err, result)
1896
 * @returns {q.Promise}
1897
 */
1898
APIClient.prototype.getWalletBalance = function(identifier, cb) {
1899
    var self = this;
1900
1901
    return self.blocktrailClient.get("/wallet/" + identifier + "/balance", null, true, cb);
1902
};
1903
1904
/**
1905
 * do HD wallet discovery for the wallet
1906
 *
1907
 * @param identifier            string      the wallet identifier
1908
 * @param [cb]                  function    callback(err, result)
1909
 * @returns {q.Promise}
1910
 */
1911
APIClient.prototype.doWalletDiscovery = function(identifier, gap, cb) {
1912
    var self = this;
1913
1914
    return self.blocktrailClient.get("/wallet/" + identifier + "/discovery", {gap: gap}, true, cb);
1915
};
1916
1917
1918
/**
1919
 * get a new derivation number for specified parent path
1920
 *  eg; m/44'/1'/0/0 results in m/44'/1'/0/0/0 and next time in m/44'/1'/0/0/1 and next time in m/44'/1'/0/0/2
1921
 *
1922
 * @param identifier            string      the wallet identifier
1923
 * @param path                  string      the parent path for which to get a new derivation,
1924
 *                                           can be suffixed with /* to make it clear on which level the derivations hould be
1925
 * @param [cb]                  function    callback(err, result)
1926
 * @returns {q.Promise}
1927
 */
1928
APIClient.prototype.getNewDerivation = function(identifier, path, cb) {
1929
    var self = this;
1930
1931
    return self.blocktrailClient.post("/wallet/" + identifier + "/path", null, {path: path}, cb);
1932
};
1933
1934
1935
/**
1936
 * delete the wallet
1937
 *  the checksum address and a signature to verify you ownership of the key of that checksum address
1938
 *  is required to be able to delete a wallet
1939
 *
1940
 * @param identifier            string      the wallet identifier
1941
 * @param checksumAddress       string      the address for your master private key (and the checksum used when creating the wallet)
1942
 * @param checksumSignature     string      a signature of the checksum address as message signed by the private key matching that address
1943
 * @param [force]               bool        ignore warnings (such as a non-zero balance)
1944
 * @param [cb]                  function    callback(err, result)
1945
 * @returns {q.Promise}
1946
 */
1947
APIClient.prototype.deleteWallet = function(identifier, checksumAddress, checksumSignature, force, cb) {
1948
    var self = this;
1949
1950
    if (typeof force === "function") {
1951
        cb = force;
1952
        force = false;
1953
    }
1954
1955
    return self.blocktrailClient.delete("/wallet/" + identifier, {force: force}, {
1956
        checksum: checksumAddress,
1957
        signature: checksumSignature
1958
    }, cb);
1959
};
1960
1961
/**
1962
 * use the API to get the best inputs to use based on the outputs
1963
 *
1964
 * the return array has the following format:
1965
 * [
1966
 *  "utxos" => [
1967
 *      [
1968
 *          "hash" => "<txHash>",
1969
 *          "idx" => "<index of the output of that <txHash>",
1970
 *          "scriptpubkey_hex" => "<scriptPubKey-hex>",
1971
 *          "value" => 32746327,
1972
 *          "address" => "1address",
1973
 *          "path" => "m/44'/1'/0'/0/13",
1974
 *          "redeem_script" => "<redeemScript-hex>",
1975
 *      ],
1976
 *  ],
1977
 *  "fee"   => 10000,
1978
 *  "change"=> 1010109201,
1979
 * ]
1980
 *
1981
 * @param identifier        string      the wallet identifier
1982
 * @param pay               array       {'address': (int)value}     coins to send
1983
 * @param lockUTXO          bool        lock UTXOs for a few seconds to allow for transaction to be created
1984
 * @param allowZeroConf     bool        allow zero confirmation unspent outputs to be used in coin selection
1985
 * @param feeStrategy       string      defaults to
1986
 * @param options
1987
 * @param [cb]              function    callback(err, utxos, fee, change)
1988
 * @returns {q.Promise}
1989
 */
1990
APIClient.prototype.coinSelection = function(identifier, pay, lockUTXO, allowZeroConf, feeStrategy, options, cb) {
1991
    var self = this;
1992
1993
    if (typeof feeStrategy === "function") {
1994
        cb = feeStrategy;
1995
        feeStrategy = null;
1996
        options = {};
1997
    } else if (typeof options === "function") {
1998
        cb = options;
1999
        options = {};
2000
    }
2001
2002
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
2003
    options = options || {};
2004
2005
    var deferred = q.defer();
2006
    deferred.promise.spreadNodeify(cb);
2007
2008
    var params = {
2009
        lock: lockUTXO,
2010
        zeroconf: allowZeroConf ? 1 : 0,
2011
        zeroconfself: (typeof options.allowZeroConfSelf !== "undefined" ? options.allowZeroConfSelf : true) ? 1 : 0,
2012
        fee_strategy: feeStrategy
2013
    };
2014
2015
    if (options.forcefee) {
2016
        params['forcefee'] = options.forcefee;
2017
    }
2018
2019
    deferred.resolve(
2020
        self.blocktrailClient.post("/wallet/" + identifier + "/coin-selection", params, pay).then(
2021
            function(result) {
2022
                return [result.utxos, result.fee, result.change, result];
2023
            },
2024
            function(err) {
2025
                if (err.message.match(/too low to pay the fee/)) {
2026
                    throw blocktrail.WalletFeeError(err);
2027
                }
2028
2029
                throw err;
2030
            }
2031
        )
2032
    );
2033
2034
    return deferred.promise;
2035
};
2036
2037
/**
2038
 * @param [cb]              function    callback(err, utxos, fee, change)
2039
 * @returns {q.Promise}
2040
 */
2041
APIClient.prototype.feePerKB = function(cb) {
2042
    var self = this;
2043
2044
    var deferred = q.defer();
2045
    deferred.promise.spreadNodeify(cb);
2046
2047
    deferred.resolve(self.blocktrailClient.get("/fee-per-kb"));
2048
2049
    return deferred.promise;
2050
};
2051
2052
/**
2053
 * send the transaction using the API
2054
 *
2055
 * @param identifier        string      the wallet identifier
2056
 * @param txHex             string      partially signed transaction as hex string
2057
 * @param paths             array       list of paths used in inputs which should be cosigned by the API
2058
 * @param checkFee          bool        when TRUE the API will verify if the fee is 100% correct and otherwise throw an exception
2059
 * @param [twoFactorToken]  string      2FA token
2060
 * @param [prioboost]       bool
2061
 * @param [cb]              function    callback(err, txHash)
2062
 * @returns {q.Promise}
2063
 */
2064
APIClient.prototype.sendTransaction = function(identifier, txHex, paths, checkFee, twoFactorToken, prioboost, cb) {
2065
    var self = this;
2066
2067
    if (typeof twoFactorToken === "function") {
2068
        cb = twoFactorToken;
2069
        twoFactorToken = null;
2070
        prioboost = false;
2071
    } else if (typeof prioboost === "function") {
2072
        cb = prioboost;
2073
        prioboost = false;
2074
    }
2075
2076
    var data = {
2077
        paths: paths,
2078
        two_factor_token: twoFactorToken
2079
    };
2080
    if (typeof txHex === "string") {
2081
        data.raw_transaction = txHex;
2082
    } else if (typeof txHex === "object") {
2083
        Object.keys(txHex).map(function(key) {
2084
            data[key] = txHex[key];
2085
        });
2086
    }
2087
2088
    return self.blocktrailClient.post(
2089
        "/wallet/" + identifier + "/send",
2090
        {
2091
            check_fee: checkFee ? 1 : 0,
2092
            prioboost: prioboost ? 1 : 0
2093
        },
2094
        data,
2095
        cb
2096
    );
2097
};
2098
2099
/**
2100
 * setup a webhook for this wallet
2101
 *
2102
 * @param identifier        string      the wallet identifier
2103
 * @param webhookIdentifier string      identifier for the webhook
2104
 * @param url               string      URL to receive webhook events
2105
 * @param [cb]              function    callback(err, webhook)
2106
 * @returns {q.Promise}
2107
 */
2108
APIClient.prototype.setupWalletWebhook = function(identifier, webhookIdentifier, url, cb) {
2109
    var self = this;
2110
2111
    return self.blocktrailClient.post("/wallet/" + identifier + "/webhook", null, {url: url, identifier: webhookIdentifier}, cb);
2112
};
2113
2114
/**
2115
 * delete a webhook that was created for this wallet
2116
 *
2117
 * @param identifier        string      the wallet identifier
2118
 * @param webhookIdentifier string      identifier for the webhook
2119
 * @param [cb]              function    callback(err, success)
2120
 * @returns {q.Promise}
2121
 */
2122
APIClient.prototype.deleteWalletWebhook = function(identifier, webhookIdentifier, cb) {
2123
    var self = this;
2124
2125
    return self.blocktrailClient.delete("/wallet/" + identifier + "/webhook/" + webhookIdentifier, null, null, cb);
2126
};
2127
2128
/**
2129
 * get all transactions for an wallet (paginated)
2130
 *
2131
 * @param identifier    string      wallet identifier
2132
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
2133
 * @param [cb]          function    callback function to call when request is complete
2134
 * @return q.Promise
2135
 */
2136
APIClient.prototype.walletTransactions = function(identifier, params, cb) {
2137
    var self = this;
2138
2139
    if (typeof params === "function") {
2140
        cb = params;
2141
        params = null;
2142
    }
2143
2144
    return self.blocktrailClient.get("/wallet/" + identifier + "/transactions", params, true, cb);
2145
};
2146
2147
/**
2148
 * get all addresses for an wallet (paginated)
2149
 *
2150
 * @param identifier    string      wallet identifier
2151
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
2152
 * @param [cb]          function    callback function to call when request is complete
2153
 * @return q.Promise
2154
 */
2155
APIClient.prototype.walletAddresses = function(identifier, params, cb) {
2156
    var self = this;
2157
2158
    if (typeof params === "function") {
2159
        cb = params;
2160
        params = null;
2161
    }
2162
2163
    return self.blocktrailClient.get("/wallet/" + identifier + "/addresses", params, true, cb);
2164
};
2165
2166
/**
2167
 * @param identifier    string      wallet identifier
2168
 * @param address       string      the address to label
2169
 * @param label         string      the label
2170
 * @param [cb]          function    callback(err, res)
2171
 * @return q.Promise
2172
 */
2173
APIClient.prototype.labelWalletAddress = function(identifier, address, label, cb) {
2174
    var self = this;
2175
2176
    return self.blocktrailClient.post("/wallet/" + identifier + "/address/" + address + "/label", null, {label: label}, cb);
2177
};
2178
2179
APIClient.prototype.walletMaxSpendable = function(identifier, allowZeroConf, feeStrategy, options, cb) {
2180
    var self = this;
2181
2182
    if (typeof feeStrategy === "function") {
2183
        cb = feeStrategy;
2184
        feeStrategy = null;
2185
    } else if (typeof options === "function") {
2186
        cb = options;
2187
        options = {};
2188
    }
2189
2190
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
2191
    options = options || {};
2192
2193
    var params = {
2194
        outputs: options.outputs ? options.outputs : 1,
2195
        zeroconf: allowZeroConf ? 1 : 0,
2196
        zeroconfself: (typeof options.allowZeroConfSelf !== "undefined" ? options.allowZeroConfSelf : true) ? 1 : 0,
2197
        fee_strategy: feeStrategy
2198
    };
2199
2200
    if (options.forcefee) {
2201
        params['forcefee'] = options.forcefee;
2202
    }
2203
2204
    return self.blocktrailClient.get("/wallet/" + identifier + "/max-spendable", params, true, cb);
2205
};
2206
2207
/**
2208
 * get all UTXOs for an wallet (paginated)
2209
 *
2210
 * @param identifier    string      wallet identifier
2211
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
2212
 * @param [cb]          function    callback function to call when request is complete
2213
 * @return q.Promise
2214
 */
2215
APIClient.prototype.walletUTXOs = function(identifier, params, cb) {
2216
    var self = this;
2217
2218
    if (typeof params === "function") {
2219
        cb = params;
2220
        params = null;
2221
    }
2222
2223
    return self.blocktrailClient.get("/wallet/" + identifier + "/utxos", params, true, cb);
2224
};
2225
2226
/**
2227
 * get a paginated list of all wallets associated with the api user
2228
 *
2229
 * @param [params]      object      pagination: {page: 1, limit: 20}
2230
 * @param [cb]          function    callback function to call when request is complete
2231
 * @return q.Promise
2232
 */
2233
APIClient.prototype.allWallets = function(params, cb) {
2234
    var self = this;
2235
2236
    if (typeof params === "function") {
2237
        cb = params;
2238
        params = null;
2239
    }
2240
2241
    return self.blocktrailClient.get("/wallets", params, true, cb);
2242
};
2243
2244
/**
2245
 * verify a message signed bitcoin-core style
2246
 *
2247
 * @param message        string
2248
 * @param address        string
2249
 * @param signature      string
2250
 * @param [cb]          function    callback function to call when request is complete
2251
 * @return q.Promise
2252
 */
2253
APIClient.prototype.verifyMessage = function(message, address, signature, cb) {
2254
    var self = this;
2255
2256
    var deferred = q.defer();
2257
    deferred.promise.nodeify(cb);
2258
    try {
2259
        var result = bitcoinMessage.verify(address, self.network.messagePrefix, message, new Buffer(signature, 'base64'));
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
2260
        deferred.resolve(result);
2261
    } catch (e) {
2262
        deferred.reject(e);
2263
    }
2264
2265
    return deferred.promise;
2266
};
2267
2268
/**
2269
 * max is 0.001
2270
 * testnet only
2271
 *
2272
 * @param address
2273
 * @param amount
2274
 * @param cb
2275
 */
2276
APIClient.prototype.faucetWithdrawl = function(address, amount, cb) {
2277
    var self = this;
2278
2279
    return self.blocktrailClient.post("/faucet/withdrawl", null, {address: address, amount: amount}, cb);
2280
};
2281
2282
/**
2283
 * send a raw transaction
2284
 *
2285
 * @param rawTransaction    string      raw transaction as HEX
2286
 * @param [cb]              function    callback function to call when request is complete
2287
 * @return q.Promise
2288
 */
2289
APIClient.prototype.sendRawTransaction = function(rawTransaction, cb) {
2290
    var self = this;
2291
2292
    return self.blocktrailClient.post("/send-raw-tx", null, rawTransaction, cb);
2293
};
2294
2295
/**
2296
 * get the current price index
2297
 *
2298
 * @param [cb]          function    callback({'USD': 287.30})
2299
 * @return q.Promise
2300
 */
2301
APIClient.prototype.price = function(cb) {
2302
    var self = this;
2303
2304
    return self.blocktrailClient.get("/price", null, false, cb);
2305
};
2306
2307
module.exports = APIClient;
2308